// STL
#include <fstream>  // std::ifstream
#include <iostream> // std::cerr
#include <string>   // std::string
#include <tuple>    // std::tuple<>, std::make_tuple()

// Boost
#include <boost/program_options.hpp> // boost::program_options


//
// DESC: Read the parameters file.
//
std::tuple<
           std::string, // gp_file
           // =====
           bool,        // create
           // -----
           int,         // ni
           int,         // no
           // -----
           std::string, // covariance_function
           double,      // theta
           // =====
           bool,        // train
           // -----
           double,      // sigma_n
           // -----
           bool,        // optimize_hyperparameters
           // -----
           std::string, // train_X_in_file
           std::string, // train_x_out_file
           // =====
           bool,        // test
           // -----
           std::string, // test_X_in_file
           std::string, // test_x_out_file
           // =====
           std::string  // prefix
           > read_param_file(
                             const std::string param_filename
                             )
{
    namespace po = boost::program_options;

    //=========================================================

    // ----- PARAMETERS -----
    std::string gp_file;
    // =====
    bool        create = false;
    // -----
    int         ni;
    int         no;
    // -----
    std::string covariance_function;
    double      theta;
    // =====
    bool        train = false;
    // -----
    double      sigma_n;
    // -----
    bool        optimize_hyperparameters;
    // -----
    std::string train_X_in_file;
    std::string train_x_out_file;
    // =====
    bool        test = false;
    // -----
    std::string test_X_in_file;
    std::string test_x_out_file;
    // =====
    std::string prefix;

    //=========================================================

    try
    {
        //=========================================================
        // SET THE OPTIONS
        //=========================================================

        po::options_description desc("options");

        desc.add_options()
        ("gp_file",                        po::value<std::string>(&gp_file)->required(),                     "GP filename (input or output)")
        // =====
        ("create.ni",                      po::value<int>(&ni),                                              "[create] type of units")
        ("create.no",                      po::value<int>(&no)->default_value(1),                            "[create] type of units")
        // -----
        ("create.covariance_function",     po::value<std::string>(&covariance_function)->default_value("squared_exponential"), "[create] type of units")
        ("create.theta",                   po::value<double>(&theta)->default_value(1.),                     "[create] type of units")
        // =====
        ("train.sigma_n",                  po::value<double>(&sigma_n)->default_value(0.),                   "[train] training method")
        // -----
        ("train.optimize_hyperparameters", po::value<bool>(&optimize_hyperparameters)->default_value(false), "[train] number of epochs (minimum)")
        // -----
        ("train.X_in_file",                po::value<std::string>(&train_X_in_file),                         "[train] X in filename")
        ("train.x_out_file",               po::value<std::string>(&train_x_out_file),                        "[train] x out filename")
        // =====
        ("test.X_in_file",                 po::value<std::string>(&test_X_in_file),                           "[test] X in filename")
        ("test.x_out_file",                po::value<std::string>(&test_x_out_file),                          "[test] X out filename")
        // =====
        ("prefix",                         po::value<std::string>(&prefix)->required(),                       "prefix for output")
        ;

        //=========================================================
        // READ THE FILE
        //=========================================================

        std::ifstream ifs(param_filename, std::ios::in);

        po::variables_map vm;
        po::store(po::parse_config_file(ifs , desc), vm);
        po::notify(vm);

        ifs.close();

        //=========================================================
        // INFER CALCULATION TYPE(S)
        //=========================================================

        // TODO: it would be nice if we could determine create / train / etc. by just checking if a block (e.g., "[train]" was specified)

        // ----- CREATE -----
        if(
           vm.count("create.ni")
           )
        {
            create = true;
        }

        // ----- TRAIN -----
        if(
           vm.count("train.X_in_file")
           )
        {
            train = true;
        }

        // ----- TEST -----
        if(
           vm.count("test.X_in_file")
           )
        {
            test = true;
        }

/*
        //=========================================================
        // CONSISTENCY / ERROR CHECK
        //=========================================================
        // NOTE: default vectors are also set here

        // TODO: it would be nice to determine interrelations between parameters, rather than all of the messy if checks below
        // TODO: ... but I am not sure that is possible with Boost program_options

        //---------------------------------------------------------
        // CREATE
        //---------------------------------------------------------
        if(create_nunits.size() != create_units_type.size())
        {
            std::cerr << "error in read_param_file(): create_nunits.size() != create_units_type.size()" << '\n';
            //        return false;
            exit(0);
        }

        if(create)
        {
            if(create_nunits.empty())
            {
                std::cerr << "error in read_param_file(): create specified, but create_nunits.empty()" << '\n';
                //        return false;
                exit(0);
            }
        }

        //---------------------------------------------------------
        // TRAIN
        //---------------------------------------------------------
        if(train)
        {
            if(train_X_file.empty())
            {
                std::cerr << "error in read_param_file(): create specified, but train_X_file.empty()" << '\n';
                //        return false;
                exit(0);
            }
        }

        // ----- LEARNING RATE ADJUSTMENTS -----
        if(train_lr_dot.size() != train_lr_adj.size())
        {
            std::cerr << "error in read_param_file(): train_lr_dot.size() != train_lr_adj.size()" << '\n';
            //        return false;
            exit(0);
        }

        if(train_lr_dot.empty())
        {
            train_lr_dot = std::vector<double>(2);
            train_lr_dot[0] = 0.5;
            train_lr_dot[1] = 0.3;
            train_lr_adj = std::vector<double>(2);
            train_lr_adj[0] = 1.05;
            train_lr_adj[1] = 1.01;
        }

        // ----- MOMENTUM ADJUSTMENTS -----
        if(train_p_dot.size() != train_p_adj.size())
        {
            std::cerr << "error in read_param_file(): train_p_dot.size() != train_p_adj.size()" << '\n';
            //        return false;
            exit(0);
        }

        if(train_p_dot.empty())
        {
            train_p_dot = std::vector<double>(1);
            train_p_dot[0] = 0.3;
            train_p_adj = std::vector<double>(1);
            train_p_adj[0] = 1.5;
        }

        //---------------------------------------------------------
        // TEST
        //---------------------------------------------------------
        if(test)
        {
            if(test_X_file.empty())
            {
                std::cerr << "error in read_param_file(): create specified, but test.X_file.empty()" << '\n';
                //        return false;
                exit(0);
            }

            if(test_H_file.empty())
            {
                std::cerr << "error in read_param_file(): create specified, but test.H_file.empty()" << '\n';
                //        return false;
                exit(0);
            }
        }
*/
    }
    catch(std::exception& e)
    {
        std::cerr << "error in read_param_file_0(): " << e.what() << '\n';
//        return false;
        exit(0);
    }
    catch(...)
    {
        std::cerr << "error in read_param_file_0(): unknown" << '\n';
//        return false;
        exit(0);
    }

    //=========================================================
    // FINALIZE AND RETURN
    //=========================================================

    return std::make_tuple(
                           gp_file,
                           // =====
                           create,
                           // -----
                           ni,
                           no,
                           // -----
                           covariance_function,
                           theta,
                           // =====
                           train,
                           // -----
                           sigma_n,
                           // -----
                           optimize_hyperparameters,
                           // -----
                           train_X_in_file,
                           train_x_out_file,
                           // =====
                           test,
                           // -----
                           test_X_in_file,
                           test_x_out_file,
                           // =====
                           prefix
                           );
}
